/**
 * Copyright 2008 Adam Ruggles.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package kiff.entity;

import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import kiff.dao.ForumAclDAO;
import kiff.security.Acl;
import kiff.security.AclEntry;
import kiff.security.Permission;
import kiff.security.Principal;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;

/**
 * Forum Access Control List.
 * @author Adam
 * @version $Id$
 */
@Entity @Table(name = "forum_acls")
@NamedQueries({
	@NamedQuery(name = ForumAclDAO.FIND_BY_OWNER, query = "select acl from ForumAcl acl where acl.owner = :owner"),
	@NamedQuery(name = ForumAclDAO.FIND_BY_NAME, query = "select acl from ForumAcl acl where acl.name = :name")
})
@org.hibernate.annotations.Cache(usage = org.hibernate.annotations.CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class ForumAcl implements Acl, CoreEntity<Integer> {
	/**
	 * Serial Version UID.
	 */
	private static final long serialVersionUID = -9016169274581764214L;

	/**
	 * The set of access control entries.
	 */
	private Set<AclEntry> entries = new HashSet<AclEntry>();

	/**
	 * Forum Acl Name for the default access control list.
	 */
	public static final String DEFAULT_NAME = "Default";

	/**
	 * The unique identifier.
	 */
	private Integer id;

	/**
	 * The name of the Forum Access Control list.
	 */
	private String name;

	/**
	 * The owner of this ACL, in the case of a Forum ACL it is the Forum object.
	 */
	private Principal owner;

	/**
	 * Default Constructor.
	 */
	public ForumAcl() {
		this.name = java.util.UUID.randomUUID().toString();
	}

	/**
	 * Constructor that sets the owner.
	 * @param owner The owner of the ForumAcl to construct.
	 */
	public ForumAcl(final Principal owner) {
		this.owner = owner;
		this.name = java.util.UUID.randomUUID().toString();
	}

	/**
	 * Constructor that sets the name.
	 * @param name The name of the ForumAcl to construct.
	 */
	public ForumAcl(final String name) {
		this.name = name;
	}

	/**
	 * Constructor that sets the name and the owner.
	 * @param name The name of the ForumAcl to construct.
	 * @param owner The owner of the ForumAcl to construct.
	 */
	public ForumAcl(final String name, final Principal owner) {
		this.name = name;
		this.owner = owner;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#addEntry(kiff.security.AclEntry)
	 */
	public void addEntry(final AclEntry entry) {
		this.entries.add(entry);
		entry.setAcl(this);
	}

	/**
	 * {@inheritDoc}
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj) {
		if (this == obj) {
			return true;
		}
		if (!(obj instanceof ForumAcl)) {
			return false;
		}
		ForumAcl rhs = (ForumAcl) obj;
		return new EqualsBuilder()
			.append(name, rhs.getName())
			.isEquals();
	}
	
	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#getAclEntries()
	 */
	@Transient
	public Enumeration<AclEntry> getAclEntries() {
		return Collections.enumeration(entries);
	}

	/**
	 * Returns the Forum Acl Entries.
	 * @return the entries in the access control list.
	 */
	@OneToMany(targetEntity = ForumAclEntry.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "acl")
	@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
	@org.hibernate.annotations.Cache(usage = org.hibernate.annotations.CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<AclEntry> getEntries() {
		return entries;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.entity.CoreEntity#getId()
	 */
	@Id @GeneratedValue(strategy = GenerationType.AUTO)
	public Integer getId() {
		return id;
	}

	/**
	 * Returns the name of the Forum ACL.
	 * @return the name of the Forum ACL.
	 */
	@Column(length = 50, unique = true)
	public String getName() {
		return name;
	}

	/**
	 * @see kiff.security.Acl#getOwner()
	 * Note: this is a one to one relationship but due to a bug in hibernate,
	 * http://opensource.atlassian.com/projects/hibernate/browse/HHH-2608.  We have to map it many to one.
	 */
	@ManyToOne(targetEntity = Forum.class, fetch = FetchType.LAZY)
	@JoinColumn(name = "forum_id", unique = true, nullable = true, updatable = false)
	public Principal getOwner() {
		return owner;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#getPermission(kiff.security.Principal)
	 */
	@Transient
	public Permission getPermission(final Principal principal) {
		for (AclEntry entry : entries) {
			if (entry.getPrincipal().equals(principal)) {
				return entry.getPermission();
			}
		}
		return null;
	}

	/**
	 * {@inheritDoc}
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return new HashCodeBuilder(1, 37)
			.append(name)
			.toHashCode();
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#hasPermission(java.util.Collection, kiff.security.Permission)
	 */
	public boolean hasPermission(final Collection<Principal> principals, final Permission permission) {
		for (Principal principal : principals) {
			if (hasPermission(principal, permission)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#hasPermission(kiff.security.Principal, kiff.security.Permission)
	 */
	public boolean hasPermission(final Principal principal, final Permission permission) {
		for (AclEntry entry : entries) {
			if (entry.getPrincipal().equals(principal)) {
				return entry.hasPermission(permission);
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#hasPermission(kiff.security.Principal[], kiff.security.Permission)
	 */
	public boolean hasPermission(final Principal[] principals, final Permission permission) {
		for (Principal principal : principals) {
			if (hasPermission(principal, permission)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#removeEntry(kiff.security.AclEntry)
	 */
	public void removeEntry(final AclEntry entry) {
		this.entries.remove(entry);
		entry.setAcl(null);
	}

	/**
	 * Sets the entries.
	 * @param entries the entries to set.
	 */
	public void setEntries(final Set<AclEntry> entries) {
		this.entries = entries;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.entity.CoreEntity#setId(java.io.Serializable)
	 */
	public void setId(final Integer id) {
		this.id = id;
	}

	/**
	 * Sets the name of the Forum ACL.
	 * @param name the name to set.
	 */
	public void setName(final String name) {
		this.name = name;
	}

	/**
	 * {@inheritDoc}
	 * @see kiff.security.Acl#setOwner(kiff.security.Principal)
	 */
	public void setOwner(final Principal owner) {
		this.owner = owner;
	}

}
